#ifndef THREADPOOL_H
#define THREADPOOL_H   
#include<vector>
#include<unordered_map>
#include<queue>
#include<memory>
#include<atomic>
#include<mutex>
#include<condition_variable>
#include<functional>
#include<future>
#include<thread>
#include<iostream>

const int TASK_MAX_THREADHOLD = 2;//1024;
const int THREAD_MAX_THREADHOLD = 100;
const int THREAD_MAX_IDLETIME = 60;

//class Any
#if 0
//Any类型：可以接收任意数据的类型
//模板的代码不可能把声明写在头文件，定义写在源文件，和普通的非模板函数是不一样的 
class Any
{
public:
	Any() = default;
	~Any() = default;

	Any(const Any&) = delete;
	Any& operator=(const Any&) = delete;

	Any(Any&&) = default;
	Any& operator=(Any&&) = default;

	//这个构造函数可以让Any类型接收任意的其他数据
	template<typename T>
	//Any(T data):base_(new Derive<T>(data))
	Any(T data) :base_(std::make_unique<Derive<T>>(data))
	{}

	//这个方法能够把Any对象里面存储的data数据提取出来
	template<typename T>
	T cast_()
	{
		//怎么从base_里面找到它所指向的Derive对象，从Derive对象里面取出data成员变量
		//基类指针转成派生类类型指针
		Derive<T>* pd = dynamic_cast<Derive<T>*>(base_.get());
		if (pd == nullptr)
		{
			throw"type is unmatch!:类型不匹配！";
		}
		return pd->data_;
	}
private:
	//基类类型
	class Base
	{
	public:
		virtual ~Base() = default;//基类对应的派生类对象如果是在堆上创建，delete基类指针，那么派生类对象的析构函数就无法调用了
	//使用default就相当于是一个大括号，只不过使用default在C++新标准中可以让编译器对指令优化更多一些
	};

	//派生类类型
	template<typename T>
	class Derive :public Base
	{
	public:
		Derive(T data) :data_(data)
		{}

		T data_;
	};
private:
	//定义一个基类的指针，指向派生类对象
	std::unique_ptr<Base>base_;
};
#endif

//class Semaphore
#if 0
//实现一个信号量类
class Semaphore
{
public:
	Semaphore(int limit = 0) :resLimit_(limit)
	{}
	~Semaphore() = default;

	//获取一个信号量资源 
	void wait()
	{
		std::unique_lock<std::mutex>lock(mtx_);
		//等待信号量有资源，没有资源会阻塞
		cond_.wait(lock, [&]()->bool {return resLimit_ > 0; });
		resLimit_--;
	}

	//增加一个信号量资源
	void post()
	{
		std::unique_lock<std::mutex>lock(mtx_);
		resLimit_++;
		cond_.notify_all();
	}
private:
	int resLimit_;
	std::mutex mtx_;
	std::condition_variable cond_;
};

#endif


//class Result
#if 0
class Task;//Task类型的前置声明
//实现接收提交到线程池的task任务执行完成后的返回值类型Result
class Result
{
public:
	Result(std::shared_ptr<Task>task, bool isValid = true);
	~Result() = default;

	//问题一: setval方法，获取任务执行完的返回值的
	void setVal(Any any);

	//问题二:get方法，用户调用这个方法获取task的返向值
	Any get();
private:
	Any any_;//存储任务的返回值
	Semaphore sem_;//任务线程若没有执行完，用户调用get会被阻塞，所以需要线程通信
	std::shared_ptr<Task>task_;//指向对应获取返回值的任务对象。强智能指针，Task对象的引用计数不为0，Task对象是不会析构的
	std::atomic_bool isValid_;//返回值是否有效
};
#endif

//class Task
#if 0
//任务抽象基类
class Task//用户自定义任务类型，从Task继承，重写run方法，实现自定义任务处理方法
{
public:
	Task();
	~Task() = default;
	void exec();//基类的一个普通方法，不会发生多态，
	//线程池里面执行的exec都是基类的exec方法，exec里面调用run();//这里发生多态调用

	void setResult(Result* res);
	virtual Any run() = 0;
private:
	Result* result_; //Result的生命周期长于Task
	//不能使用智能指针shared_ptr，因为会造成交叉引用，无法释放对象，造成资源泄漏
};

#endif

//线程池支持的模式
enum class PoolMode
{
	MODE_FIXED,  //固定数量的线程
	MODE_CACHED, //线程数量可动态增长
};
//线程类型
class Thread
{
public:
	//线程函数对象类型
	using ThreadFunc = std::function<void(int)>;//返回值void 没有参数的函数对象类型

	Thread(ThreadFunc func)
		:func_(func)
		, threadId_(generateId_++)
	{}
	~Thread() = default;

	void start()//启动线程
	{
		//创建一个线程来执行一个线程函数
		std::thread t(func_, threadId_);// C++11来说线程对象t 和线程函数func_
		t.detach();//设置分离线程，真真正正执行线程函数的线程要一直执行的
	}
	int getId()const//获取线程ID
	{
		return threadId_;
	} 
private:
	ThreadFunc func_;
	static int generateId_;
	int threadId_;//保存线程id
};
int Thread::generateId_ = 0;

//线程池类型
class ThreadPool
{
public:
	ThreadPool() 
		:initThreadSize_(0)
		, taskSize_(0)
		, curThreadSize_(0)
		, threadSizeThreadHold_(THREAD_MAX_THREADHOLD)
		, taskQueMaxThreadHold_(TASK_MAX_THREADHOLD)
		, poolmode_(PoolMode::MODE_FIXED)
		, isPoolRunning_(false)
		, idleThreadSize_(0)
	{}

	~ThreadPool()
	{
		isPoolRunning_ = false;
		//notEmpty_.notify_all();

		//等待线程池里面所有的线程返回
		//线程池里面此时只有2种状态的线程：阻塞 & 正在执行任务中
		std::unique_lock<std::mutex>lock(taskQueMtx_);
		notEmpty_.notify_all();
		exitCond_.wait(lock, [&]()->bool {return threads_.size() == 0; });
	}

	ThreadPool(const ThreadPool&) = delete;
	ThreadPool& operator=(const ThreadPool&) = delete;


	void setMode(PoolMode mode)//设置线程池工作模式
	{
		if (CheckRunningState())
		{
			return;
		}
		poolmode_ = mode;
	}
	//void setInitThreadSize(int size);//设置初始的线程数量
	void start(int initThreadSize = std::thread::hardware_concurrency())//开启线程池,初始化线程数量为CPU核心数量
	{
		isPoolRunning_ = true;
		//记录初始线程个数
		initThreadSize_ = initThreadSize;
		curThreadSize_ = initThreadSize;

		//创建线程对象
		for (int i = 0; i < initThreadSize_; i++)
		{
			//创建thread线程对象的时候，把线程函数给到thread线程对象
			//threads_.emplace_back(new Thread(std::bind(&ThreadPool::threadFunc,this)));
			auto ptr = std::make_unique<Thread>(std::bind(&ThreadPool::threadFunc, this, std::placeholders::_1));//给当前线程绑定线程函数
			//threads_.emplace_back(std::move(ptr));
			int threadid = ptr->getId();
			threads_.emplace(threadid, std::move(ptr));

		}

		//启动所有线程std::vector<Thread*>threads_
		for (int i = 0; i < initThreadSize_; i++)
		{
			threads_[i]->start();//需要执行一个线程函数
			idleThreadSize_++;//每启动一个线程就是一个空闲线程，因此记录空闲线程的数量
		}
	}
	void setTaskQueMaxThreadHold(int num)//设置task任务队列上线阈值
	{
		taskQueMaxThreadHold_ = num;
	}

	//设置线程池cached模式下线程阈值
	void setThreadsizeThreshHold(int threshhold)
	{
		if (CheckRunningState())
		{
			return;
		}
		if (PoolMode::MODE_CACHED == poolmode_)
		{
			threadSizeThreadHold_ = threshhold;
		}
	}

	//使用可变参模板编程，让submitTask可以接收任意任务函数和任意数量的参数 
	//pool.submitTask( sum2,1 ,2，3);
	template<typename Func,typename ...Args>
	//std::future<返回值类型>// 返回值类型是属于任务函数的（Func有返回值，函数名，参数列表），所以接收函数返回值就需要使用auto
	auto submitTask(Func&& func, Args&&...argc)->std::future<decltype(func(argc...))>
		//函数返回值是一个future，但是future的尖括号里面是要填写返回值类型，返回值类型根本获取不了；所以就需要通过->后面的表达式推导出来auto的类型
	{
		//打包任务，放入任务队列 
		using RType = decltype(func(argc...));//返回值类型
		auto task = std::make_shared<std::packaged_task<RType()>>(
			std::bind(std::forward<Func>(func), std::forward<Args>(argc)...)
			);//创建了一个packaged_task函数对象，
		//使用它打包了一个返回值是RType,不带参数的函数对象，不带参数不是没有参数，而是使用绑定器将参数给函数对象绑定上了
		std::future<RType>result = task->get_future();
	
		//获取锁
		std::unique_lock<std::mutex>lock(taskQueMtx_);

		//线程的通信  通过信号量等待任务队列有空余
		//用户提交任务，最长不能阻塞超过1s，否则判断提交任务失败，返回
		// 
		//while (taskQue_.size() == taskQueMaxThreadHold_)
		//{
		//	notFull_.wait(lock);//1.把线程状态改变；2.释放当前抢到的锁
		//}
		//上面三行等价于notFull_.wait(lock,[&]()->bool {taskQue_.size() < taskQueMaxThreadHold_; })
		if (!notFull_.wait_for(lock, std::chrono::seconds(1),
			[&]()->bool {return taskQue_.size() < (size_t)taskQueMaxThreadHold_; }))
		{
			//表示notFull_等待1s，条件依然没有满足
			std::cerr << "task queue is full,submit task fail." << std::endl;
			auto task = std::make_shared<std::packaged_task<RType()>>(
				[]()->RType {return RType(); }
			);
			(*task)();
			return task->get_future();
		}
		 
		//如果有空余，把任务放入任务队列
		// taskQue_.emplace(sp);
		taskQue_.emplace([task]() {(*task)(); });//task是一个智能指针，解引用就是packaged_task
		taskSize_++;

		//因为新放了任务，任务队列肯定不空了，在notEmpty_上进行通知,赶快分配线程执行任务
		notEmpty_.notify_all();

		//cached 模式 任务处理比较紧急场景:小而快的任务     需要根据任务数量和空闲线程的数量，判断是否需要创建新的线程出来 ?
		if (poolmode_ == PoolMode::MODE_CACHED
			&& taskSize_ > idleThreadSize_
			&& curThreadSize_ < threadSizeThreadHold_)
		{
			std::cout << ">>>>create new thread!" << std::endl;
			//创建新线程对象
			auto ptr = std::make_unique<Thread>(std::bind(&ThreadPool::threadFunc, this, std::placeholders::_1));
			//threads_.emplace_back(std::move(ptr));
			int threadid = ptr->getId();
			threads_.emplace(threadid, std::move(ptr));
			threads_[threadid]->start();//启动线程
			curThreadSize_++;//修改线程个数
			idleThreadSize_++;
		}


		//返回任务的Result对象 
		return result;
	}
private:
	//定义线程执行函数 
	void threadFunc(int threadid)//返回值void 没有参数(有参数this,已经绑进去了，相当于没有参数)
	{
		auto lastTime = std::chrono::high_resolution_clock().now();

		//所有任务必须执行完成，线程池才可以回收所有线程资源

		//while(isPoolRunning_)//线程不断循环从任务队列里面取任务，而不是取一个执行完了就完了
		for (;;)
		{
			Task task;
			//先获取锁 
			{
				std::unique_lock<std::mutex>lock(taskQueMtx_);
				std::cout << "尝试获取任务 threadFunc 线程ID：" << std::this_thread::get_id() << std::endl;
				//代码重构
				// cached模式下，有可能已经创建了很多的线程，但是空闲时间超过60s，应该把多余的线程
				//结束回收掉???
				//结束回收掉（超过initThreadsize_数量的线程要进行回收)
				//当前时间 - 上一次线程执行的时间  >  60s

				//每一秒返回一次 //怎么区分:超时返回?  还是有任务待执行返回
				//while (isPoolRunning_ && taskQue_.size() == 0)//没有任务
				while (taskQue_.size() == 0)
				{
					if (!isPoolRunning_)//阻塞的线程被唤醒后被回收
					{
						//正在执行任务的线程，执行完任务后线程被回收
						threads_.erase(threadid);//std::this_thread::get_id();
						std::cout << "threadid:" << std::this_thread::get_id() << "exit!" << std::endl;
						exitCond_.notify_all();//一个线程资源回收，得通知一下阻塞在exitCond_的析构函数，让析构函数判断是否满足条件，如果满足析构函数向下执行，不然析构函数会一直阻塞

						return;
					}

					if (poolmode_ == PoolMode::MODE_CACHED)
					{
						//条件变量超时返回了
						if (std::cv_status::timeout == notEmpty_.wait_for(lock, std::chrono::seconds(1)))
						{                                         //每次阻塞1s后返回
							auto now = std::chrono::high_resolution_clock().now();
							auto dur = std::chrono::duration_cast<std::chrono::seconds>(now - lastTime);
							if (dur.count() >= THREAD_MAX_IDLETIME
								&& curThreadSize_ > initThreadSize_)
							{
								//开始回收当前线程
								//记录线程相关数量的值需要修改
								//把线程对象从线程列表容器中删除
								//线程函数返回，相应的线程也就结束了。关键是如何删除当前线程函数对应的线程对象
								//把线程对象从线程列表容器中删除  没有办法   threadFunc 《 = 》 thread对象
								//threadid => thread对象=>删除
								threads_.erase(threadid);//std::this_thread::get_id();
								curThreadSize_--;
								idleThreadSize_--;
								std::cout << "threadid:" << std::this_thread::get_id() << "exit!" << std::endl;
								return;
							}
						}
					}
					else//fixed模式：只要线程没有执行任务就一直阻塞
					{
						//等待notEmpty条件
						notEmpty_.wait(lock);
					}
					/*
					//线程池要结束，回收线程资源
					if (!isPoolRunning_)//阻塞的线程被唤醒后被回收
					{
						threads_.erase(threadid);//std::this_thread::get_id();
						std::cout << "threadid:" << std::this_thread::get_id() << "exit!" << std::endl;
						exitCond_.notify_all();//一个线程资源回收，得通知一下阻塞在exitCond_的析构函数，让析构函数判断是否满足条件，如果满足析构函数向下执行，不然析构函数会一直阻塞
						return;//结束线程函数，就是结束当前线程了!

					}
					*/
				}
				/*
				* //线程池要结束，回收线程资源
				if (!isPoolRunning_)//阻塞的线程被唤醒后被回收
				{
					break;
				}
				*/


				idleThreadSize_--;//满足条件，执行任务队列中的任务，所以空闲线程减减

				std::cout << " threadFunc 线程ID：" << std::this_thread::get_id() << " 获取任务成功" << std::endl;

				//不空就从任务队列中取一个任务
				task = taskQue_.front();
				taskQue_.pop();
				taskSize_--;

				//如果依然有剩余任务，继续通知其它得线程执行任务
				if (taskQue_.size() > 0)
				{
					notEmpty_.notify_all();
				}

				//取出一个任务，进行通知,通知可以继续提交生产任务
				notFull_.notify_all();
			}//局部对象出作用域会自动析构，将锁释放

			//应该释放锁，其他线程可以获得锁，从而获取任务。防止一个线程将锁拿的时间太长，表现出来就是多线程程序只有一个线程工作

			//当前线程负责执行这个任务
			if (task != nullptr)
			{
				//task->run();//执行当前任务；把任务的返回值通过setVal方法给到Result方法
				//task->exec();//把run方法封装，就可以做比run方法更多的事情
				task();//执行function<void()>
			}

			idleThreadSize_++;
			lastTime = std::chrono::high_resolution_clock().now();//更新线程执行完的时间
		}
	}
	//检测pool的运行状态
	bool CheckRunningState()const
	{
		return isPoolRunning_;
	}

private:
	//std::vector<std::unique_ptr<Thread>>threads_;  //线程列表  //容器的析构--》里面的元素进行析构--》智能指针析构--》管理的堆内存进行析构
	std::unordered_map<int, std::unique_ptr<Thread>>threads_;

	size_t initThreadSize_;        //初始的线程数量		
	std::atomic_uint idleThreadSize_;//记录空闲线程的数量
	std::atomic_uint curThreadSize_; //记录当前线程池里面线程的总数量
	int threadSizeThreadHold_;//线程数量的上限阈值

	//Task==>函数对象
	using Task = std::function<void()>;
	//任务队列使用裸指针是不行的，因为无法保证用户传进来的任务是生命周期很长的，也有可能是一个临时量
	std::queue<Task>taskQue_;//任务队列
	std::atomic_uint taskSize_;//任务的数量
	int taskQueMaxThreadHold_;//任务队列 数量的上限阈值

	std::mutex taskQueMtx_;//保证任务队列的线程安全
	std::condition_variable notFull_;//表示任务队列不满
	std::condition_variable notEmpty_;//表示任务队列不空
	std::condition_variable exitCond_;//等待线程资源全部回收

	PoolMode poolmode_;//当前线程池的工作模式
	std::atomic_bool isPoolRunning_;// 表示当前线程池的启动状态

};





#endif
